package analiseSintatica.Regras;

import java.util.LinkedList;

import AnaliseLexicaFinal.EstruturaDeDados.LinkToken;
import analiseSintatica.ErroSintaticoException;
import analiseSintatica.estruturadados.NoArvore;

/**
 * Contem o esqueleto para a implementacao das regras para a analise sintatica
 * @author amferraz
 * @author Elisson
 */
public abstract class Regra {

    protected LinkedList<String> primeiro = null;
    protected LinkedList<String> seguinte = null;

    
    /**
     * Instancie sua Regra passando a lista de tokens atual. Quando voce mandar
     * analisar, essa lista de tokens serah consumida atraves do metodo
     * <code>analisar()</code>. O metodo <code>analisar()</code> tambem retorna
     * o noh da arvore correspondente aa regra analisada.
     * 
     * @param listaTokens a lista de tokens do codigo
     */
//    public Regra() {
//    }

     

    /**
     * Realiza analise sintatica correspondente ao nome da classe
     * @param listaTokens lista atual de tokens do codigo ainda nao analisada
     * @return - NoArvore o noh correspondente a essa regra
     * @throws Exception caso nao seja reconhecido o token nessa regra.
     */
    public abstract NoArvore analisar(LinkedList<LinkToken> listaTokens) throws ErroSintaticoException;

    

    /**
     * Realiza um operacao de uniao entre duas listas
     * @param lista1
     * @param lista2
     * @return Lista com a uniao
     */
    public static LinkedList<String> getUniao(LinkedList<String> lista1, LinkedList<String> lista2) {
        for (int i = 0; i < lista2.size(); i++) {
            if (!lista1.contains(lista2.get(i))) {
                lista1.add(lista2.get(i));
            }
        }
        return lista1;
    }
    
    public static LinkedList<String> getUniao(LinkedList<String> lista, String nova) {
        if (!lista.contains(nova)) {
            lista.add(nova);
        }
        return lista;
    }

    /**
     * Reconhece terminal a partir de um token
     * @param listatokens - Tokens do codigo
     * @param token - Token que deve ser reconhecido
     * @return - Sub-arvore sintatica
     * @throws ErroSintaticoException
     */
    public NoArvore reconheceToken(LinkedList<LinkToken> listatokens, String token, String estrutura) throws ErroSintaticoException {
        if (listatokens.size()==0 ||listatokens.getFirst().getToken().equals(token)) {
           // System.out.println("Consumi terminal" + token + "Da estrutura"+ estrutura);
            listatokens.removeFirst();
            return new NoArvore(token, true);
        } else {
            ErroSintaticoException erro = new ErroSintaticoException();
            erro.addErros("Falta " + token, estrutura, listatokens.getFirst().getLinha());
            throw erro;
        }
    }

    /**
     * Reconhece terminal a partir de um lexema
     * @param listatokens - Tokens do codigo
     * @param token - Token desejado
     * @param lexema - Lexema que deve ser reconhecido
     * @return - Sub-arvore sintatica
     * @throws ErroSintaticoException
     */
    public NoArvore reconheceLexema(LinkedList<LinkToken> listatokens, String token, String lexema, String estrutura) throws ErroSintaticoException {
        if (listatokens.getFirst().getToken().equals(token) && listatokens.getFirst().getLexema().equals(lexema)) {
            // System.out.println("Consumi terminal" + lexema + "Da estrutura"+ estrutura);
            listatokens.removeFirst();
            return new NoArvore(token, true);
        } else {
            ErroSintaticoException erro = new ErroSintaticoException();
            erro.addErros("Falta " + lexema, estrutura, listatokens.getFirst().getLinha());
           // System.out.println(erro.getErros());
            throw erro;
        }
    }

    /**
     * Verifica se determinado token existe em um conjunto
     * @param conjunto - Conjunto de Strings
     * @param token
     * @return
     */
   public boolean listaContem(LinkedList<String> conjunto, LinkToken token) {
        if ((token.getToken().equals("operador_logico")) || (token.getToken().equals("operador_igualdade")) ||
                (token.getToken().equals("operador_relacional")) || (token.getToken().equals("operador_adicao")) ||
                (token.getToken().equals("operador_multiplicacao")) || (token.getToken().equals("incremento")) ||
                (token.getToken().equals("delimitador"))) {
            return conjunto.contains(token.getLexema());
        } else {
            return conjunto.contains(token.getToken());
        }
    }

    /**
     * Reconhece não-terminal a partir de um objeto Regra
     * Ex.: RegraFor naoTerminal = new RegraFor(); Então
     * passo naoTerminal como parâmetro
     * @param listatokens - Tokens do codigo
     * @param naoTerminal - Próximo Não Terminal a ser derivado
     * @return - Sub-arvore sintatica
     */
    public static NoArvore reconhecerNaoTerminal(LinkedList<LinkToken> listaTokens, Regra naoTerminal) throws ErroSintaticoException {
        LinkToken token = listaTokens.getFirst();
        if (naoTerminal.getPrimeiro().contains(token.getToken())|| naoTerminal.getPrimeiro().contains(token.getLexema())) {
            return naoTerminal.analisar(listaTokens);
        } else {
            return null;

        }
    }




    /**
     * Adicionar aa lista <code>primeiro</code> os elementos primeiro da regra
     */
    protected abstract void setPrimeiro();

    /**
     * Adicionar aa lista <code>seguinte</code> os elementos seguintes aa regra
     */
    protected abstract void setSeguinte();



    /**
     * Funcao primeiro para cada nao-terminal
     * @return Lista contendo os terminais que estao na funcao primeiro
     */
    public LinkedList<String> getPrimeiro() {
        if (this.primeiro == null) {
            this.primeiro = new LinkedList<String>();
            this.setPrimeiro();
        }

        return this.primeiro;
    }


    /**
     * Funcao seguinte para cada nao-terminal
     * @return Lista contendo os terminais que estao na funcao seguinte
     */
    
    public LinkedList<String> getSeguinte() {
        if (this.seguinte == null) {
            this.seguinte = new LinkedList<String>();
            this.setSeguinte();
        }
        return this.seguinte;
    }



}
